/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.google.code.peersim.pastry.controls;

import com.google.code.peersim.pastry.protocol.PastryNode;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import peersim.config.Configuration;
import peersim.core.CommonState;
import peersim.core.Control;
import peersim.core.Network;
import peersim.dynamics.OscillatingNetwork;
import peersim.util.FileNameGenerator;

/**
 * This {@link Control} class simulates dynamic network oscillations by both adding
 * and remove nodes. You can control the time-window within which such operations
 * are allowed, and you can also configure whether nodes either looking for resources
 * or acting as sources spreading resources, should be saved.
 *
 * @author frusso
 * @version 0.1
 * @since 0.1
 */
public class PastryOverlayResizer extends OscillatingNetwork {

  /**
   * Start activity simulated-time.
   */
  public static final String MIN_SIMULATION_TIME = "minTime";
  /**
   * End activity simulated-time.
   */
  public static final String MAX_SIMULATION_TIME = "maxTime";
  /**
   * File to log to.
   */
  public static final String LOG_FILE = "log";
  /**
   * Whether to log or not.
   */
  public static final String DO_LOG = "doLog";
  /**
   * Should source nodes not be removed?
   */
  public static final String SAVE_SENDING_NODES = "saveSendingNodes";
  /**
   * Should nodes looking for resoruces not be removed?
   */
  public static final String SAVE_SEARCHING_NODES = "saveSearchingNodes";
  /**
   * Whether the component is active or not.
   */
  private static boolean enabled = true;

  /**
   * Enable the component.
   */
  public static void enable() {
    enabled = true;
  }

  /**
   * Disable the component.
   */
  public static void disable() {
    enabled = false;
  }
  
  private String fileName;
  private PrintStream stream;
  private int minTime;
  private int maxTime;
  private boolean doLog, saveSendingNodes, saveSearchingNodes;
  private long added, removed;

  /**
   * Constructor.
   * 
   * @param prefix
   * @throws java.io.FileNotFoundException
   */
  public PastryOverlayResizer(String prefix) throws FileNotFoundException {
    super(prefix);
    minTime = Configuration.getInt(prefix+"."+MIN_SIMULATION_TIME);
    maxTime = Configuration.getInt(prefix+"."+MAX_SIMULATION_TIME);
    saveSendingNodes = Configuration.getBoolean(prefix+"."+SAVE_SENDING_NODES);
    saveSearchingNodes = Configuration.getBoolean(prefix+"."+SAVE_SEARCHING_NODES);
    doLog = Configuration.getBoolean(prefix + "." + DO_LOG);
    if(doLog) {
      fileName = new FileNameGenerator(Configuration.getString(prefix + "."+LOG_FILE), ".log").nextCounterName();
      stream = new PrintStream(new FileOutputStream(fileName));
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean execute() {
    boolean stop = super.execute();
    if(CommonState.getTime()==CommonState.getEndTime()-1) {
      printFinalStats();
    }
    return stop;
  }

  /**
   * {@inheritDoc}
   *
   * Performs iff the component is enabled and we are in the configured time-window.
   *
   * @see PastryOverlayResizer#enable()
   * @see PastryOverlayResizer#disable() 
   */
  @Override
  protected void add(int n) {
    if(enabled && isInTime()) {
      if(doLog)
        stream.println(CommonState.getTime()+") adding "+n+" nodes");
      super.add(n);
      added+=n;
    }
  }

  /**
   * {@inheritDoc}
   *
   * Performs iff the component is enabled and we are in the configured time-window.
   *
   * @see PastryOverlayResizer#enable()
   * @see PastryOverlayResizer#disable()
   */
  @Override
  protected void remove(int n) {
    if(enabled && isInTime()) {
      if(doLog)
        stream.println(CommonState.getTime()+") removing "+n+" nodes");
      _remove(n);
      removed+=n;
    }
  }

  /**
   * Performs the actual node(s)-removal operation according to what stated by the
   * {@link PastryOverlayResizer#saveSearchingNodes} and  {@link PastryOverlayResizer#saveSendingNodes}
   * configuration parameters.
   *
   * @param n How many nodes should be removed
   */
  protected void _remove(int n) {
    for (int i = 0; i < n; ++i) {
      int nodeIndex = CommonState.r.nextInt(Network.size());
      if( (saveSendingNodes && !((PastryNode)Network.get(nodeIndex)).isSending())
        ||
         saveSearchingNodes && !((PastryNode)Network.get(nodeIndex)).isSearching()) {
        Network.remove(nodeIndex);
      }
    }
  }

  /**
   * Are we in the activity-allowed time-window?
   * 
   * @return The answer to the former question
   */
  private boolean isInTime() {
    return CommonState.getTime()>minTime && CommonState.getTime()<maxTime;
  }

  private void printFinalStats() {
    System.err.println(this.getClass().getName()+":\nadded "+added+" nodes\nremoved "+removed+" nodes\n");
  }
}